fix(auth): replace raw Cookie header with httpx cookie jar to fix #273#276
fix(auth): replace raw Cookie header with httpx cookie jar to fix #273#276AmanSuryavanshi-1 wants to merge 5 commits intoteng-lin:mainfrom
Conversation
…g-lin#273 Cross-domain redirects (e.g., to accounts.google.com for token refresh) now properly retain cookies by using httpx.Cookies() jar instead of raw Cookie header. This fixes auth failures after ~10 minutes when Google's short-lived cookies expire and trigger a 302 redirect. Changes: - auth.py: fetch_tokens() now uses httpx.Cookies() jar - _core.py: ClientCore.open() uses cookies jar for HTTP client - _core.py: update_auth_headers() replaces cookies jar on token refresh - _core.py: add _build_cookies_jar() helper for cookie jar creation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces manual "Cookie" request-header usage with httpx.Cookies jars across client and auth flows; ClientCore builds/uses a cookie jar and merges cookies into the client's jar. Auth helpers now extract/build cookies with original domains (including accounts.google.com) and token fetches use cookie jars. Tests added for cookie-preservation. Changes
Sequence Diagram(s)sequenceDiagram
participant ClientCore as ClientCore
participant Auth as Auth Helpers
participant HTTPX as httpx.AsyncClient
participant Google as Google (notebooklm / accounts)
ClientCore->>Auth: build_httpx_cookies_from_storage(path)
Auth-->>ClientCore: httpx.Cookies jar (domains preserved)
ClientCore->>HTTPX: AsyncClient(cookies=jar)
ClientCore->>HTTPX: GET https://notebooklm.google.com/ (follow_redirects)
HTTPX->>Google: GET notebooklm.google.com (sends domain cookies)
Google-->>HTTPX: 302 -> accounts.google.com
HTTPX->>Google: GET accounts.google.com (sends appropriate jar cookies)
Google-->>HTTPX: final HTML (200)
HTTPX-->>ClientCore: response HTML
ClientCore->>ClientCore: extract CSRF/session tokens
ClientCore->>HTTPX: update_auth_headers() -> merges/sets cookies on client jar
ClientCore->>Auth: save_cookies_to_storage(http_client.cookies, storage_path)
Auth-->>ClientCore: persisted storage_state.json (if applicable)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request transitions the HTTP client from using raw 'Cookie' headers to 'httpx.Cookies' jars to support cross-domain redirects during authentication. Feedback indicates that forcing the '.google.com' domain on all cookies may break functionality for other domains like '.googleusercontent.com' and poses a security risk regarding credential leakage. Additionally, the current implementation of updating auth headers may inadvertently overwrite session state provided by the server, and the logic for building the cookie jar is duplicated and should be centralized.
| for name, value in self.auth.cookies.items(): | ||
| # Use .google.com domain to cover all subdomains including | ||
| # accounts.google.com (used for token refresh redirects) | ||
| cookies.set(name, value, domain=".google.com") |
There was a problem hiding this comment.
Forcing the ".google.com" domain on all cookies is a problematic side effect of using a flat dictionary (self.auth.cookies) to populate the cookie jar. While this ensures cookies are sent to accounts.google.com during redirects, it also means that cookies originally intended for other domains—such as .googleusercontent.com (which is used for media downloads and is explicitly allowed in auth.py)—will now be sent to .google.com and not to their intended destination. This will likely break authenticated downloads from Google's content servers. Additionally, ensure that when making requests to external URLs using these cookies, the domain is validated against an allowlist of trusted domains to prevent credential leakage.
References
- When making requests to external URLs using authentication cookies, always validate that the URL's domain is on an allowlist of trusted domains to prevent credential leakage.
| raise RuntimeError("Client not initialized. Use 'async with' context.") | ||
| self._http_client.headers["Cookie"] = self.auth.cookie_header | ||
| # Replace cookies jar with updated auth tokens | ||
| self._http_client.cookies = self._build_cookies_jar() |
There was a problem hiding this comment.
Re-assigning the cookie jar here will overwrite any cookies that the server might have updated or set during the session (e.g., via Set-Cookie headers in previous requests). Since self.auth.cookies is a static dictionary initialized at startup and never updated, this call effectively reverts the client's session state to its initial values. Given that csrf_token and session_id are handled separately and are not stored in this cookie jar, this reset may be unnecessary and potentially harmful to session persistence.
| cookie_jar = httpx.Cookies() | ||
| for name, value in cookies.items(): | ||
| cookie_jar.set(name, value, domain=".google.com") |
There was a problem hiding this comment.
This logic for building a cookie jar is duplicated in ClientCore._build_cookies_jar. It should be centralized (e.g., as a method on AuthTokens) to ensure consistent behavior. Additionally, as noted in the core client review, forcing the .google.com domain for all cookies may break functionality for other domains like googleusercontent.com which are used for media downloads.
There was a problem hiding this comment.
Pull request overview
Updates NotebookLM authentication HTTP handling to use httpx.Cookies instead of a raw Cookie header, aiming to preserve session cookies across Google redirect flows (notebooklm → accounts → notebooklm) described in Issue #273.
Changes:
- Switch
auth.fetch_tokens()to build and pass anhttpx.Cookiesjar (vs. a rawCookie:header). - Switch
ClientCore.open()to configure the sharedhttpx.AsyncClientwith a cookies jar (vs.headers["Cookie"]). - Update
ClientCore.update_auth_headers()to refresh the client’s cookies via a new_build_cookies_jar()helper.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/notebooklm/auth.py |
Builds an httpx.Cookies jar for token-fetching requests that may redirect across Google hosts. |
src/notebooklm/_core.py |
Configures the core httpx.AsyncClient to use a cookies jar and adds a helper to construct it from AuthTokens. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Build httpx.Cookies jar instead of raw header to properly handle | ||
| # cross-domain redirects (e.g., to accounts.google.com for refresh) | ||
| cookie_jar = httpx.Cookies() | ||
| for name, value in cookies.items(): | ||
| cookie_jar.set(name, value, domain=".google.com") |
There was a problem hiding this comment.
fetch_tokens() builds a cookie jar from the filtered cookies dict, but extract_cookies_from_storage() currently excludes cookies whose domain is accounts.google.com (because _is_allowed_auth_domain() only allows ALLOWED_COOKIE_DOMAINS and regional .google.*). If the cross-host refresh flow depends on accounts-scoped cookies (as described in #273), they will never make it into this jar, so redirects to accounts.google.com can still fail. Consider expanding the auth-domain allowlist to include accounts.google.com (and possibly .accounts.google.com) or switching token fetching to use a domain-preserving httpx.Cookies source (similar to load_httpx_cookies()).
| # Build httpx.Cookies jar instead of raw header to properly handle | |
| # cross-domain redirects (e.g., to accounts.google.com for refresh) | |
| cookie_jar = httpx.Cookies() | |
| for name, value in cookies.items(): | |
| cookie_jar.set(name, value, domain=".google.com") | |
| # Build an httpx.Cookies jar instead of a raw Cookie header so cookies | |
| # participate in redirect handling. The input is a flattened name/value | |
| # mapping, so domain specificity has already been lost by this point. | |
| # To preserve current behavior while allowing redirects through the Google | |
| # Accounts host, register each cookie for both the general Google domain | |
| # and the concrete accounts.google.com host. | |
| cookie_jar = httpx.Cookies() | |
| for name, value in cookies.items(): | |
| cookie_jar.set(name, value, domain=".google.com") | |
| cookie_jar.set(name, value, domain="accounts.google.com") |
| if not self._http_client: | ||
| raise RuntimeError("Client not initialized. Use 'async with' context.") | ||
| self._http_client.headers["Cookie"] = self.auth.cookie_header | ||
| # Replace cookies jar with updated auth tokens | ||
| self._http_client.cookies = self._build_cookies_jar() |
There was a problem hiding this comment.
update_auth_headers() replaces the entire AsyncClient.cookies jar with a new jar rebuilt from self.auth.cookies. Since self.auth.cookies is not updated by refresh_auth() (it only updates CSRF/session_id), this can discard any cookies that were refreshed via Set-Cookie during redirect flows (e.g., the short-lived __Secure-*PSIDRTS cookies), undermining the goal of using a persistent cookie jar. Prefer keeping the existing jar and only mutating it when the cookie values actually change (or explicitly syncing refreshed cookies back into self.auth.cookies before rebuilding).
| async with httpx.AsyncClient(cookies=cookie_jar) as client: | ||
| response = await client.get( | ||
| "https://notebooklm.google.com/", | ||
| headers={"Cookie": cookie_header}, | ||
| follow_redirects=True, | ||
| timeout=30.0, | ||
| ) |
There was a problem hiding this comment.
The new redirect-handling behavior in fetch_tokens() is not directly asserted in tests. There are existing pytest_httpx patterns in this repo to inspect outgoing requests; adding an assertion that the redirected request to accounts.google.com still includes a Cookie header (and/or that the request chain completes when cookies are valid) would prevent regressions of #273.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/notebooklm/_core.py`:
- Around line 179-180: The current code replaces the httpx cookie jar object
(self._http_client.cookies = self._build_cookies_jar()), which drops any cookies
httpx added during requests or redirects; instead, call _build_cookies_jar() to
get the new cookies and merge them into the existing jar
(self._http_client.cookies) — e.g., iterate the cookies from
_build_cookies_jar() and set/update them into self._http_client.cookies
(preserving domain/path/attributes) rather than assigning a new httpx.Cookies
object.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7051df54-2ea6-4006-9bb5-cab3ef3ee544
📒 Files selected for processing (2)
src/notebooklm/_core.pysrc/notebooklm/auth.py
…coping and lifetimes - Add accounts.google.com to ALLOWED_COOKIE_DOMAINS for token refresh - Fix update_auth_headers() to merge cookies instead of replacing (preserves live cookies received from redirects like accounts.google.com) - Add extract_cookies_with_domains() to preserve original cookie domains - Add build_httpx_cookies_from_storage() for proper domain-aware cookie jars - Add fetch_tokens_with_domains() variant that uses original domains - Add TestCrossDomainCookiePreservation tests for redirect cookie handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/integration/test_core.py`:
- Around line 462-490: The test currently only checks cookie presence; to prove
merge (not replace) change test_update_auth_headers_merges_not_replaces to set a
unique sentinel value (e.g., "redirect-only-sentinel") on http_client.cookies
for "SID" (domain=".google.com") before calling core.update_auth_headers(), then
after calling core.update_auth_headers() assert that
http_client.cookies.get("SID", domain=".google.com") == "redirect-only-sentinel"
(and optionally assert other expected tokens still exist). Locate this logic
around NotebookLMClient usage and
core.update_auth_headers()/http_client.cookies.set/get to implement the stronger
equality assertion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9e59a70d-9893-4a16-bffd-abefc58686b8
📒 Files selected for processing (3)
src/notebooklm/_core.pysrc/notebooklm/auth.pytests/integration/test_core.py
🚧 Files skipped from review as they are similar to previous changes (2)
- src/notebooklm/_core.py
- src/notebooklm/auth.py
…tion This addresses CodeRabbit review feedback on PR teng-lin#276 by asserting exact sentinel values survive update_auth_headers(), rather than just checking for 'not None' which would incorrectly pass if cookies were replaced.
…prevent 48-hour expiration
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/notebooklm/auth.py (2)
682-688: Misleading comment: prioritization logic is ineffective here.The key is
(name, domain), so("SID", ".google.com")and("SID", ".google.de")are distinct keys. Theelif domain == ".google.com"branch at line 686 can never override a cookie from a different domain—it only handles duplicate entries with the same (name, domain) tuple, which is unlikely in practice.If the intent is to preserve all domain-specific cookies (which appears correct for cross-domain redirect handling), the comment at line 683 ("Prefer .google.com over regional domains") is misleading. Consider clarifying or removing the dead prioritization branch.
🔧 Suggested simplification
key = (name, domain) - # Prefer .google.com over regional domains - if key not in cookie_map: - cookie_map[key] = value - elif domain == ".google.com": - # .google.com takes precedence - cookie_map[key] = value + # Store each (name, domain) pair; duplicates within the same domain are rare + if key not in cookie_map: + cookie_map[key] = value🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/notebooklm/auth.py` around lines 682 - 688, The comment claiming ".google.com" is preferred is misleading because the map key is (name, domain) so domain-specific cookies are stored separately; update the code by either removing the dead prioritization branch (the "elif domain == '.google.com': cookie_map[key] = value" block) and its misleading comment, or change the key logic if you truly want to prefer .google.com (e.g., use name-only keys and implement explicit precedence). Refer to the variables key, cookie_map, domain and name to locate the code to modify and ensure the remaining comment accurately reflects that domain-specific cookies are preserved.
794-798: Log the exception during temp file cleanup.The
try-except-passsilently swallows errors during temp file cleanup. While this is non-critical, logging helps with debugging unexpected permission or filesystem issues.🔧 Proposed fix
- try: - temp_path.unlink(missing_ok=True) - except Exception: - pass + try: + temp_path.unlink(missing_ok=True) + except Exception as cleanup_err: + logger.debug("Failed to clean up temp file %s: %s", temp_path, cleanup_err)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/notebooklm/auth.py` around lines 794 - 798, Replace the silent swallow in the temp file cleanup so exceptions are logged: where temp_path.unlink(missing_ok=True) is wrapped in try/except, change the except to capture the exception (e) and log it instead of pass — e.g., call an appropriate logger (module logger or logging.exception) to record the error and context including temp_path; target the block around temp_path.unlink to implement this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/notebooklm/auth.py`:
- Around line 874-896: The code reads and uses the httpx Response (final_url,
response.text) after closing the AsyncClient; move all uses of response —
computing final_url, calling is_google_auth_redirect(final_url), and calling
extract_csrf_from_html(response.text, final_url) and
extract_session_id_from_html(response.text, final_url) — inside the async with
httpx.AsyncClient(...) block so the response body is still valid; ensure
response.raise_for_status() remains, perform the redirect check and token
extractions before exiting the context, then log and return csrf, session_id.
- Around line 851-896: The function fetch_tokens_with_domains is unused; either
delete it or wire it into the auth flow—specifically replace or augment the
current token-loading path (e.g., AuthTokens.from_storage or the code in
download.py that loads tokens) to call fetch_tokens_with_domains(path) when you
need cookies preserved with original domains, or if you plan to defer, remove
the function and add a brief TODO in the PR description; ensure any callers
expect the returned tuple (csrf, session_id) and handle its exceptions
(FileNotFoundError, httpx.HTTPError, ValueError).
---
Nitpick comments:
In `@src/notebooklm/auth.py`:
- Around line 682-688: The comment claiming ".google.com" is preferred is
misleading because the map key is (name, domain) so domain-specific cookies are
stored separately; update the code by either removing the dead prioritization
branch (the "elif domain == '.google.com': cookie_map[key] = value" block) and
its misleading comment, or change the key logic if you truly want to prefer
.google.com (e.g., use name-only keys and implement explicit precedence). Refer
to the variables key, cookie_map, domain and name to locate the code to modify
and ensure the remaining comment accurately reflects that domain-specific
cookies are preserved.
- Around line 794-798: Replace the silent swallow in the temp file cleanup so
exceptions are logged: where temp_path.unlink(missing_ok=True) is wrapped in
try/except, change the except to capture the exception (e) and log it instead of
pass — e.g., call an appropriate logger (module logger or logging.exception) to
record the error and context including temp_path; target the block around
temp_path.unlink to implement this change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 38899296-865f-4292-85d0-62ea9b97c896
📒 Files selected for processing (3)
src/notebooklm/_core.pysrc/notebooklm/auth.pysrc/notebooklm/cli/helpers.py
🚧 Files skipped from review as they are similar to previous changes (1)
- src/notebooklm/_core.py
| async def fetch_tokens_with_domains(path: Path | None = None) -> tuple[str, str]: | ||
| """Fetch CSRF token and session ID using storage with original domains. | ||
|
|
||
| Loads cookies from storage preserving their original domains and makes | ||
| an authenticated request. This version is preferred when you need proper | ||
| cross-domain redirect handling with the original cookie domains intact. | ||
|
|
||
| Args: | ||
| path: Path to storage_state.json. If provided, takes precedence over env vars. | ||
|
|
||
| Returns: | ||
| Tuple of (csrf_token, session_id) | ||
|
|
||
| Raises: | ||
| FileNotFoundError: If storage file doesn't exist. | ||
| httpx.HTTPError: If request fails. | ||
| ValueError: If tokens cannot be extracted from response. | ||
| """ | ||
| logger.debug("Fetching CSRF and session tokens with original cookie domains") | ||
|
|
||
| # Load cookies with original domains preserved | ||
| cookie_jar = build_httpx_cookies_from_storage(path) | ||
|
|
||
| async with httpx.AsyncClient(cookies=cookie_jar) as client: | ||
| response = await client.get( | ||
| "https://notebooklm.google.com/", | ||
| follow_redirects=True, | ||
| timeout=30.0, | ||
| ) | ||
| response.raise_for_status() | ||
|
|
||
| final_url = str(response.url) | ||
|
|
||
| # Check if we were redirected to login | ||
| if is_google_auth_redirect(final_url): | ||
| raise ValueError( | ||
| "Authentication expired or invalid. " | ||
| "Redirected to: " + final_url + "\n" | ||
| "Run 'notebooklm login' to re-authenticate." | ||
| ) | ||
|
|
||
| csrf = extract_csrf_from_html(response.text, final_url) | ||
| session_id = extract_session_id_from_html(response.text, final_url) | ||
|
|
||
| logger.debug("Authentication tokens obtained successfully") | ||
| return csrf, session_id |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for usages of fetch_tokens_with_domains
rg -n 'fetch_tokens_with_domains' --type=pyRepository: teng-lin/notebooklm-py
Length of output: 174
Remove or integrate fetch_tokens_with_domains() — the function is unused.
The new function defined at lines 851–896 is not called anywhere in the codebase. Either remove it or integrate it into the actual authentication flow (e.g., in download.py or AuthTokens.from_storage()). If it's planned for a follow-up, document that intention or defer the implementation to a separate PR.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/notebooklm/auth.py` around lines 851 - 896, The function
fetch_tokens_with_domains is unused; either delete it or wire it into the auth
flow—specifically replace or augment the current token-loading path (e.g.,
AuthTokens.from_storage or the code in download.py that loads tokens) to call
fetch_tokens_with_domains(path) when you need cookies preserved with original
domains, or if you plan to defer, remove the function and add a brief TODO in
the PR description; ensure any callers expect the returned tuple (csrf,
session_id) and handle its exceptions (FileNotFoundError, httpx.HTTPError,
ValueError).
| async with httpx.AsyncClient(cookies=cookie_jar) as client: | ||
| response = await client.get( | ||
| "https://notebooklm.google.com/", | ||
| follow_redirects=True, | ||
| timeout=30.0, | ||
| ) | ||
| response.raise_for_status() | ||
|
|
||
| final_url = str(response.url) | ||
|
|
||
| # Check if we were redirected to login | ||
| if is_google_auth_redirect(final_url): | ||
| raise ValueError( | ||
| "Authentication expired or invalid. " | ||
| "Redirected to: " + final_url + "\n" | ||
| "Run 'notebooklm login' to re-authenticate." | ||
| ) | ||
|
|
||
| csrf = extract_csrf_from_html(response.text, final_url) | ||
| session_id = extract_session_id_from_html(response.text, final_url) | ||
|
|
||
| logger.debug("Authentication tokens obtained successfully") | ||
| return csrf, session_id |
There was a problem hiding this comment.
Response accessed after client context exit.
The response object is used (lines 882–893) after exiting the async with httpx.AsyncClient(...) block at line 880. While response.text is typically available because the body was fully read during response.raise_for_status(), this pattern is fragile and inconsistent with fetch_tokens() which extracts tokens inside the context.
🔧 Proposed fix: move extraction inside the context
async with httpx.AsyncClient(cookies=cookie_jar) as client:
response = await client.get(
"https://notebooklm.google.com/",
follow_redirects=True,
timeout=30.0,
)
response.raise_for_status()
- final_url = str(response.url)
+ final_url = str(response.url)
- # Check if we were redirected to login
- if is_google_auth_redirect(final_url):
- raise ValueError(
- "Authentication expired or invalid. "
- "Redirected to: " + final_url + "\n"
- "Run 'notebooklm login' to re-authenticate."
- )
+ # Check if we were redirected to login
+ if is_google_auth_redirect(final_url):
+ raise ValueError(
+ "Authentication expired or invalid. "
+ "Redirected to: " + final_url + "\n"
+ "Run 'notebooklm login' to re-authenticate."
+ )
- csrf = extract_csrf_from_html(response.text, final_url)
- session_id = extract_session_id_from_html(response.text, final_url)
+ csrf = extract_csrf_from_html(response.text, final_url)
+ session_id = extract_session_id_from_html(response.text, final_url)
- logger.debug("Authentication tokens obtained successfully")
- return csrf, session_id
+ logger.debug("Authentication tokens obtained successfully")
+ return csrf, session_id🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/notebooklm/auth.py` around lines 874 - 896, The code reads and uses the
httpx Response (final_url, response.text) after closing the AsyncClient; move
all uses of response — computing final_url, calling
is_google_auth_redirect(final_url), and calling
extract_csrf_from_html(response.text, final_url) and
extract_session_id_from_html(response.text, final_url) — inside the async with
httpx.AsyncClient(...) block so the response body is still valid; ensure
response.raise_for_status() remains, perform the redirect check and token
extractions before exiting the context, then log and return csrf, session_id.
Closes #273
Fixes Issue #273 where short-lived 10 minute Google cookies triggering a 302 redirect to accounts.google.com would drop the raw Cookie headers and kill the session. Migrated the auth fetchers and the RPC ClientCore to use a persistent
httpx.Cookiesjar.Summary
Problem: Auth fails after ~10 minutes due to raw Cookie header being dropped on cross-domain redirects (Google's cookies expire every 10-15 minutes and trigger a 302 to
accounts.google.comto refresh them).Solution: Replace raw
Cookie:header withhttpx.Cookies()jar which properly handles cross-domain redirects.Changes Made
src/notebooklm/auth.py-fetch_tokens(): Useshttpx.Cookies()jar with domain=".google.com"src/notebooklm/_core.py-ClientCore.open(): Uses cookies jar for HTTP clientsrc/notebooklm/_core.py-update_auth_headers(): Replaces cookies jar on token refresh_build_cookies_jar()helper methodTest Plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests